// 源码测试
#include "utility/ojData.h"
#include "utility/fileio.h"
#include "utility/jsonObject.h"
#include "utility/defer.h"
#include "include/runcode.h"
#include <filesystem>
#include <functional>
#include <limits>
#include <type_traits>
#include "judge_types/baseJudge.hpp"

#include <string>
#include <iostream>
#include <variant>
using namespace std;

vector<string> warning_dirs;
vector<string> error_dirs;

double time_eps = 0.05;
double time_tolerance = 100;
double memory_eps = 0.1;


struct CompareTime {};
struct CompareMemory {};
struct ComparePassExample {};
struct CompareTestExample {};
struct CompareResult {};

class attribute_compare_t {
public:
    template <class Tag, class T, class U>
    bool operator()(Tag, const T& a, const U& b) {
        cout << "\tfirst[" << a << "], second[" << b << "]" << endl;
        return a == b;
    }
    template <class Tag>
    bool operator()(Tag, const JsonObject& a, const JsonObject& b) {
        try {
            cout << "\tfirst[" << a.json() << "], second[" << b.json() << "]" << endl;
            return a.equal(b);
        } catch(...) {
            return false;
        }
    }
    template <class Tag>
    bool operator()(Tag, const JsonObjectPtr& a, const JsonObjectPtr& b) {
        return (*this)(Tag{}, *a, *b);
    }

    template <class T, class U, class = enable_if_t<is_arithmetic_v<T> && is_arithmetic_v<U>>>
    bool operator()(CompareTime, T a, U b) {
        cout << "\tfirst[" << a << "], second[" << b << "]" << endl;
        double eps = std::abs(static_cast<double>(a) - static_cast<double>(b));
        return eps < time_tolerance || eps / std::max(a, b) < time_eps;
    }

    bool operator()(CompareTime, const JsonObjectPtr& a, const JsonObjectPtr& b) {
        try {
            return (*this)(CompareTime{}, a->asInt(), b->asInt());
        } catch(...) {
            return false;
        }
    }

    template <class T, class U, class = enable_if_t<is_arithmetic_v<T> && is_arithmetic_v<U>>>
    bool operator()(CompareMemory, T a, U b) {
        cout << "\tfirst[" << a << "], second[" << b << "]" << endl;
        return std::abs(static_cast<double>(a) - static_cast<double>(b)) / std::max(a, b) < memory_eps;
    }

    bool operator()(CompareMemory, const JsonObjectPtr& a, const JsonObjectPtr& b) {
        try {
            return (*this)(CompareMemory{}, a->asInt(), b->asInt());
        } catch(...) {
            return false;
        }
    }

    template <class T, class U, class = enable_if_t<is_integral_v<T> && is_integral_v<U>>>
    bool operator()(CompareResult, T a, U b) {
        cout << "\tfirst[" << a << "], second[" << b << "]" << endl;
        return codeToStr[a] == codeToStr[b];
    }

    bool operator()(CompareResult, const JsonObjectPtr& a, const JsonObjectPtr& b) {
        try {
            return (*this)(CompareResult{}, a->asString(), b->asString());
        } catch(...) {
            return false;
        }
    }
};

static auto compare_fun = attribute_compare_t{};

using compare_tag_t = variant<
                                CompareTime,
                                CompareMemory,
                                ComparePassExample,
                                CompareTestExample,
                                CompareResult
                            >;
map<string, compare_tag_t> compare_tag_map = {
    {"time", CompareTime{}},
    {"memory", CompareMemory{}},
    {"pass_example", ComparePassExample{}},
    {"test_example", CompareTestExample{}},
    {"result", CompareResult{}}
};

void test_unit(const string& unit_dir, RuncodeParams run_params) {
    Log::instance().setOutputFile(osJoin(unit_dir, "runout.log"), std::ios::out | std::ios::trunc);
    Log::instance().setErrorFile(osJoin(unit_dir, "runerror.log"), std::ios::out | std::ios::trunc);

    string params_file = osJoin(unit_dir, "params.json");
    auto params_json = JsonObject::parseDictStr(fileToStr(params_file));
    parse_run_params(run_params, params_json);

    auto res = runJudge(run_params);

    Log::instance().setOutputFile("");
    Log::instance().setErrorFile("");

    cout << unit_dir << endl;
    string expectation_file = osJoin(unit_dir, "expectation.json");
    if (!std::filesystem::exists(expectation_file)) {
        cout << "\tno expectation.json, skip" << endl;
        cout << "\tresult:" << res.json() << endl;
        return;
    }

    auto expectation_json = JsonObject::parseDictStr(fileToStr(expectation_file));
    auto result_json = JsonObject::parseDictStr(res.json());

    const auto& expectation_dict = expectation_json->asDict();
    const auto& result_dict = result_json->asDict();
    bool pass = true;

    for (const auto& [key, val] : expectation_dict) {
        auto check_it = result_dict.find(key);
        if (check_it == result_dict.end()) {
            continue;
        }

        auto compare_tag_it = compare_tag_map.find(key);
        if (compare_tag_it == compare_tag_map.end()) {
            continue;
        }

        auto compare_fun_impl = [&](auto tag) -> bool {
            return compare_fun(tag, val, check_it->second);
        };
        auto compare_result = std::visit(compare_fun_impl, compare_tag_it->second);
        if (!compare_result) {
            cout << "\tcompare fail, field(" << key << ")" << endl;
            pass = false;
        } else {
            cout << "\tcompare pass, field(" << key << ")" << endl;
        }
    }
    if (!pass) {
        error_dirs.push_back(unit_dir);
    }
}

void test_recusive(const string& path, RuncodeParams run_params) {
    auto default_params_file = osJoin(path, "default_params.json");
    if (existsFile(default_params_file)) {
        auto tmp_json = JsonObject::parseDictStr(fileToStr(default_params_file));
        parse_run_params(run_params, tmp_json);
    }

    auto all_files = searchFiles(path);
    for (auto& file : all_files) {
        if (file == "params.json") {
            test_unit(path, run_params);
            continue;
        }

        file = osJoin(path, file);
        if (std::filesystem::is_directory(file)) {
            test_recusive(file, run_params);
            continue;
        }
    }

}


int main(int argc, char* argv[])
{
#ifdef ENABLE_DB_JUDGE
    databaseParams.init(
        "localhost",
        3306,
        "root",
        "root",
        "user",
        "user"
    );
#endif
    struct RuncodeParams default_params;
    const string test_dir = argc > 1 ? argv[1] : "run_test";

    if (!isDirPath(test_dir)) {
        return 1;
    }

    default_params = RuncodeParams {
        .language = "c++",                   // 语言类型
        .judgeType = NORMAL_JUDGE,                          // 题型
        .filename = "",                   // 当前用户代码所在文件
        .initPath = "",                   // 题目所在路径
        .dataPath = "",                   // 题目所在路径
        .spjPath = "",
        .timeLimit = 1000,                   // 时间限制
        .memoryLimit = 65536,         // 内存限制
        .compareFlag = 0,    // 文本比对模式
        .ruler = OI_CONTENT, //赛制
        .querySet = RETURN_FIRST_OUTPUT | RETURN_ALL_OUTPUT | RETURN_ALL_RESULT,                // 结果集
        .eps = "1e-3",                  // 误差
        .userCachePath = "",             // 将用户代码拷贝到file地址下，代码将在此文件同级目录下编译，若一些需要项目构建的语言，则将此作为项目名
        .spjCachePath = "",               // 将特判文件拷贝到spjfile地址下，代码将在此文件同级目录下编译，若一些需要项目构建的语言，则将此作为项目名
    };

    test_recusive(test_dir, default_params);

    cout << "warning unit size:" << warning_dirs.size() << endl;
    for (const auto& unit_dir : warning_dirs) {
        cout << "\t" << "\033[0m\033[1;34m" << unit_dir << "\033[m" << endl;
    }
    cout << "error unit size:" << error_dirs.size() << endl;
    for (const auto& unit_dir : error_dirs) {
        cout << "\t" << "\033[0m\033[1;31m" << unit_dir << "\033[0m" << endl;
    }

    return 0;
}